import { EventEmitter, Injectable } from '@angular/core';
import { Graph, Node, Path, Shape } from '@antv/x6';
import { Clipboard } from '@antv/x6-plugin-clipboard';
import { History } from '@antv/x6-plugin-history';
import { Keyboard } from '@antv/x6-plugin-keyboard';
import { Selection } from '@antv/x6-plugin-selection';
import { Snapline } from '@antv/x6-plugin-snapline';
import { Stencil } from "@antv/x6-plugin-stencil";
import { groupBy } from "lodash";
import { AlgoUnit } from './algo-unit';
import { AlgoUnitMetadata } from './algo-unit-metadata';
import { AlgoGraphConfig, AlgoLinkConfig, AlgoPortConfig, AlgoUnitConfig } from './models';

@Injectable()
export class FlowGraphDesignService {
  flowGraph?: AlgoGraphConfig;

  unregisterGraphTypes() {
    Graph.unregisterNode(AlgoUnit.shapeName);
  }
  registerGraphTypes() {
    Graph.registerNode(AlgoUnit.shapeName, AlgoUnit, true);
    Graph.registerEdge("algo-edge", {
      inherit: 'edge',
      attrs: {
        line: {
          stroke: '#C2C8D5',
          strokeWidth: 1,
          targetMarker: {
            name: 'classic',
            size: 7,
          },
        },
      },
    }, true,);

    Graph.registerConnector('algo-connector', (s, e) => {
      const offset = 8;
      const deltaX = Math.abs(e.x - s.x);
      const control1 = Math.floor((deltaX / 3) * 2);

      const h1 = { x: s.x + offset + control1, y: s.y };
      const h2 = { x: e.x - offset - control1, y: e.y };

      return Path.normalize(
        `M ${s.x} ${s.y}
         L ${s.x + offset} ${s.y}
         C ${h1.x} ${h1.y} ${h2.x} ${h2.y} ${e.x - offset} ${e.y}
         L ${e.x} ${e.y}
        `,
      );
    }, true);
  }
  graph!: Graph;
  stencil!: Stencil;

  nodeDblclick: EventEmitter<AlgoUnitMetadata> = new EventEmitter();
  constructor() { }

  initGraph(container: HTMLElement, dndContainer: HTMLElement) {
    const graph = new Graph({
      container: container,
      background: {
        color: '#F2F7FA',
      },
      highlighting: {
        magnetAvailable: magnetAvailabilityHighlighter,
        magnetAdsorbed: {
          name: 'stroke',
          args: {
            attrs: {
              fill: '#fff',
              stroke: '#31d0c6',
            },
          },
        },
      },
      connecting: {
        snap: true,
        allowBlank: false,
        allowLoop: false,
        allowNode: false,
        allowEdge: false,
        allowPort: true,
        highlight: true,

        connector: 'algo-connector',
        connectionPoint: 'anchor',
        anchor: 'center',
        router: {
          name: 'er',
          args: {
            direction: 'H',
          },
        },
        createEdge() {
          return new Shape.Edge({
            shape: "algo-edge",
            zIndex: -1,
            attrs: {
              line: {
                stroke: '#A2B1C3',
                strokeDasharray: '5 5',
                strokeWidth: 1,
                targetMarker: {
                  name: 'classic',
                  size: 7,
                },
              },
            },
          });
        },
        validateConnection(args) {
          const { sourceView, sourcePort, targetPort, targetView, sourceMagnet, targetCell, targetMagnet } = args;
          if (!targetMagnet) {
            return false;
          }

          if (targetMagnet.getAttribute('port-group') !== 'in') {
            return false;
          }

          if (targetView) {
            const node = targetView.cell;
            if (node instanceof AlgoUnit) {

              const portId = targetMagnet.getAttribute('port');
              const usedInPorts = node.getUsedInPorts(graph);
              if (usedInPorts.find((port) => port && port.id === portId)) {
                return false;
              }
            }
          }

          return true;
        },
      },
      translating: {
        restrict: false
      },
    });

    graph
      // .use(new Transform({ resizing: true, rotating: true, }))
      .use(new Selection({ rubberband: true, showNodeSelectionBox: true, }))
      .use(new Snapline())
      .use(new Keyboard())
      .use(new Clipboard())
      .use(new History());

    const stencil = new Stencil({
      target: graph,
      stencilGraphWidth: 200,
      stencilGraphHeight: 180,
      collapsable: true,
    });
    dndContainer.appendChild(stencil.container);

    this.graph = graph;
    this.stencil = stencil;

    this._bindGraphKeys();
  }

  private _bindGraphKeys() {
    const { graph } = this;
    (window as any).__x6_instances__ = [graph];

    graph.on("node:added", ({ node, index, options }) => {
      if (node instanceof AlgoUnit) {
        const id = node.id;
        const cfg = node.data;
        cfg.Id = id;
        node.setData(cfg);
      }
    });

    // delete
    graph.bindKey(['delete'], () => {
      const cells = graph.getSelectedCells();
      if (cells.length) {
        graph.removeCells(cells);
      }
    });

    graph.on('edge:connected', ({ edge }) => {
      edge.attr({
        line: {
          strokeDasharray: '',
        },
      });
    });

    graph.on("node:dblclick", ({ node, e }) => {
      e.preventDefault();
      const { id } = node;
      const data = node.getData();
      this.nodeDblclick.emit({ ...data, Id: id });
    });

    graph.on("edge:dblclick", ({ edge, e }) => {
      e.preventDefault();
      // const { id, source } = edge;
      // const SourcePortId = edge.getSourcePortId();
      // const TargetPortId = edge.getTargetPortId();
      // console.log(source, SourcePortId, TargetPortId);
    });
  }

  startDrag(e: MouseEvent, item: AlgoUnitMetadata) {
    const meta: Node.Metadata = AlgoUnit.getMeta(item);
    const node = this.graph.createNode(meta);
    // this.dnd.start(node, e);
  }

  saveToJson() {
    const data = this.graph.toJSON();
    localStorage.setItem("flow-graph-design", JSON.stringify(data));
  }

  loadFromJson() {
    const json = localStorage.getItem("flow-graph-design");
    if (json) {
      const data = JSON.parse(json);
      this.graph.fromJSON(data);
    }
  }

  initStencil(types: AlgoUnitMetadata[]) {
    const groups = groupBy(types, (type) => type.Group);
    for (const key in groups) {
      if (Object.prototype.hasOwnProperty.call(groups, key)) {
        const items = groups[key];
        const hegiht = items.length * 40 + 15;
        const group: Stencil.Group = {
          name: key,
          collapsable: false,
          graphHeight: hegiht,
          layoutOptions: {
            columns: 1,
            rowHeight: 40,
          }
        };
        this.stencil.addGroup(group);
        const nodes = items.map((item) => {
          const meta = AlgoUnit.getMeta(item);
          return this.graph.createNode(meta);
        });
        this.stencil.load(nodes, key);
      }
    }
  }

  loadAlgoGraph(flowGraph: AlgoGraphConfig) {
    const { graph } = this;
    graph.clearCells();

    this.flowGraph = flowGraph;
    const { Units, Links } = flowGraph;

    Units.forEach((item) => {
      const algoMeta: AlgoUnitMetadata = {
        Id: item.Id,
        Name: item.Name,
        Group: item.Group,
        In: item.Ports.filter(a => a.Type == "in").map(a => a.Id),
        Out: item.Ports.filter(a => a.Type == "out").map(a => a.Id),
      };
      const meta = AlgoUnit.getMeta(algoMeta);

      const { X: x, Y: y } = item.Positon;
      const { Width: width, Height: height } = item.Size;
      meta.position = { x, y };
      meta.size = { width, height };

      graph.addNode(meta);
    });

    Links.forEach((item) => {
      const { SourceId, SourcePortId, TargetId, TargetPortId } = item;

      const node = graph.getNodes().find(a => a.id == SourceId);

      graph.addEdge({
        shape: "algo-edge",
        source: SourceId,
        sourcePort: SourcePortId,
        target: TargetId,
        targetPort: TargetPortId,
      });
    });
  }
  getAlgoGraph(): AlgoGraphConfig {

    const nodes = this.graph.getNodes().filter(a => a instanceof AlgoUnit);
    const units = nodes.map(a => {
      const meta: AlgoUnitMetadata = a.data;
      const { x: X, y: Y } = a.position();
      const { width: Width, height: Height } = a.size();

      const ports = a.ports.items.map<AlgoPortConfig>(a => ({ Type: <any>a.group!, Id: a.id! }));
      const cfg: AlgoUnitConfig = {
        Id: meta.Id,
        Name: meta.Name,
        Group: meta.Group,
        Ports: ports,
        Positon: { X, Y },
        Size: { Width, Height },
      };

      return cfg;
    });
    const edges = this.graph.getEdges();
    const links = edges.map<AlgoLinkConfig>(a => {
      const SourceId = a.getSourceCellId();
      const SourcePortId = a.getSourcePortId()!;
      const TargetId = a.getTargetCellId();
      const TargetPortId = a.getTargetPortId()!;

      return {
        SourceId,
        SourcePortId,
        TargetId,
        TargetPortId,
      };
    });

    return {
      Id: this.flowGraph?.Id || '',
      Units: units,
      Links: links,
    };
  }
}


// 高亮
const magnetAvailabilityHighlighter = {
  name: 'stroke',
  args: {
    attrs: {
      fill: '#fff',
      stroke: '#47C769',
    },
  },
};